import * as fs from "node:fs";
import { ImageParser } from "./pixivLib/pageParser/pixiv/ImageParser.mjs";
import { PixivRetryReadable } from './pixivLib/util/PixivRetryReadable.mjs';
import {getAuthorInfoById, searchTags} from "./pixivLib/util/PixpediaApi.mjs";
import PluginWorksResponseDTO from "./lsLib/model/dto/PluginWorksResponseDTO.mjs";
import SiteAuthorPluginDTO from "./lsLib/model/dto/SiteAuthorPluginDTO.mjs";
import SiteTagPluginDTO from "./lsLib/model/dto/SiteTagPluginDTO.mjs";
import AuthorRank from "./lsLib/constant/AuthorRank.mjs";
import PluginCreateTaskResponseDTO from "./lsLib/model/dto/PluginCreateTaskResponseDTO.mjs";
import ResourcePluginDTO from "./lsLib/model/dto/ResourcePluginDTO.mjs";
import TaskPluginData from "./model/TaskPluginData.mjs";
import {PixivLogin} from "./pixivLib/util/PixivLogin.mjs";
import PixivTokens from "./pixivLib/model/PixivTokens.mjs";
import AppPixivAPI from "./pixivLib/util/AppPixivAPI.mjs";
import {Restrict} from "./constant/Restrict.mjs";
import BrowserPixivAPI from "./pixivLib/util/BrowserPixivAPI.mjs";
import UnifiedWorksInfo from "./pixivLib/model/UnifiedWorksInfo.mjs";
import OAuthFailed from "./pixivLib/error/OAuthFailed.mjs";
import AccessTokenRefreshFailed from "./pixivLib/error/AccessTokenRefreshFailed.mjs";
import PluginCreateParentTaskResponseDTO from "./lsLib/model/dto/PluginCreateParentTaskResponseDTO.mjs";

export default class PixivTaskHandler {
  /**
   * Pixiv的域名
   * @type string
   */
  static PIXIV_DOMAIN = 'www.pixiv.net'

  /**
   * 插件工具
   * @type PluginTool
   */
  pluginTool

  /**
   * pixiv令牌
   * @type PixivTokens
   */
  pixivTokens

  /**
   * @param pluginTool {PluginTool}
   * @param pluginData {string}
   */
  constructor(pluginTool, pluginData) {
    this.pluginTool = pluginTool
    try {
      const pluginDataObj = JSON.parse(pluginData)
      this.pixivTokens = new PixivTokens(pluginDataObj.accessToken, pluginDataObj.refreshToken)
    } catch (ignoredError) {
      this.pixivTokens = new PixivTokens()
    }
  }

  /**
   * 创建任务
   * @param url {string} 需解析的url
   * @return Promise<Task[]>
   */
  async create(url) {
    const illustId = ImageParser.parseIllustId(url)
    let resUrlList
    const unifiedWorksInfo = new UnifiedWorksInfo()
    const taskPluginData = new TaskPluginData()
    let authorId

    // 先请求浏览器端的接口，如果是非限制级则可以一次请求获取所有信息
    const browserApi = new BrowserPixivAPI()
    const browserIllustDetailResponse = await browserApi.illustDetail(illustId)
    const browserIllustDetail = browserIllustDetailResponse.body
    if (browserIllustDetail && browserIllustDetail.xRestrict === Restrict.NOT_RESTRICTED) {
      unifiedWorksInfo.title = browserIllustDetail.title
      unifiedWorksInfo.description = browserIllustDetail.description
      // 作品资源
      const tempResponse = await browserApi.illustPages(illustId)
      const pages = tempResponse.body
      if (!tempResponse.error && pages && pages.length > 0) {
        resUrlList = pages.map(page => page.urls.original)
      }
      // 标签
      unifiedWorksInfo.browserTags = browserIllustDetail.tags
      // 作者
      authorId = browserIllustDetail.userId
    } else {
      this.pluginTool.pluginLogUtil.info(`【${browserIllustDetail.title}】为限制级作品，使用移动端接口`)
      // 限制级作品使用移动端的接口
      const appApi = new AppPixivAPI(this.pixivTokens.accessToken)
      // 此次resolveOAuth仅处理accessToken为空的情况
      await this.resolveOAuth()
      appApi.setAccessToken(this.pixivTokens.accessToken)
      let appIllustDetail
      try {
        appIllustDetail = await appApi.illustDetail(illustId, true)
      } catch (error) {
        if (error instanceof OAuthFailed) {
          // 已确认accessToken过期或错误，调用resolveOAuth刷新令牌或请求用户登录
          this.pluginTool.pluginLogUtil.info(`accessToken过期或错误，清除缓存的accessToken`)
          this.pixivTokens.accessToken = undefined
          await this.resolveOAuth()
          if (!this.pixivTokens.accessToken) {
            throw new OAuthFailed('获取令牌失败！')
          }
          appApi.setAccessToken(this.pixivTokens.accessToken)
          appIllustDetail = await appApi.illustDetail(illustId, true)
        } else {
          throw error
        }
      }
      unifiedWorksInfo.title = appIllustDetail.title
      unifiedWorksInfo.description = appIllustDetail.caption
      // 作品资源
      if (appIllustDetail.page_count === 1) {
        resUrlList = [appIllustDetail.meta_single_page.original_image_url]
      } else {
        resUrlList = appIllustDetail.meta_pages.map(page => page.image_urls.original)
      }
      // 标签
      unifiedWorksInfo.appTags = appIllustDetail.tags
      // 作者
      authorId = appIllustDetail.user.id
    }

    // 作者信息
    const siteAuthor = new SiteAuthorPluginDTO()
    siteAuthor.siteDomain = PixivTaskHandler.PIXIV_DOMAIN
    siteAuthor.authorRank = AuthorRank.RANK_0
    // 查询作者详情信息
    const rawUserInfo = await getAuthorInfoById(authorId)
    if (rawUserInfo) {
      siteAuthor.siteAuthorId = rawUserInfo.userId
      siteAuthor.authorName = rawUserInfo.name
      siteAuthor.fixedAuthorName = browserIllustDetail.userAccount
      siteAuthor.introduce = rawUserInfo.comment
    }
    taskPluginData.siteAuthor = [siteAuthor]

    // 标签信息
    const bTags = unifiedWorksInfo.browserTags?.tags
    // 查询标签详细信息
    const queryTag = async (tagName) => {
      try {
        const rawSiteTag = await searchTags(tagName)
        const siteTag = new SiteTagPluginDTO()
        siteTag.siteTagId = String(rawSiteTag.tag)
        siteTag.siteTagName = rawSiteTag.tag
        siteTag.description = rawSiteTag.abstract
        siteTag.siteDomain = PixivTaskHandler.PIXIV_DOMAIN
        taskPluginData.siteTags.push(siteTag)
      } catch (error) {
        this.pluginTool.pluginLogUtil.error(`查询标签【${tagName}】失败，error: `, error)
      }
    }
    if (bTags && bTags?.length > 0) {
      for (const bTag of bTags) {
        await queryTag(bTag.tag)
      }
    } else {
      const aTags = unifiedWorksInfo.appTags
      if (aTags && aTags.length > 0) {
        for (const aTag of aTags) {
          await queryTag(aTag.name)
        }
      }
    }

    taskPluginData.unifiedWorksInfo = unifiedWorksInfo
    // 创建任务列表
    const parentTask = new PluginCreateParentTaskResponseDTO()
    parentTask.taskName = unifiedWorksInfo.title
    parentTask.siteWorksId = illustId
    parentTask.url = url
    parentTask.pluginData = { ...taskPluginData }
    parentTask.siteDomain = PixivTaskHandler.PIXIV_DOMAIN
    const childTasks = []
    parentTask.children = childTasks
    if (resUrlList.length === 1) {
        const idMatch = resUrlList[0].match(/https:\/\/i\.pximg\.net\/img-original\/img\/\d{4}\/\d{2}\/\d{2}\/\d{2}\/\d{2}\/\d{2}\/(\d+_p\d+)\.[a-zA-Z]+/)
        if (!idMatch) {
            this.pluginTool.pluginLogUtil.warn('找不到资源url')
        } else if (!idMatch[1]) {
            this.pluginTool.pluginLogUtil.warn('资源url中未匹配到id')
        } else {
            const pluginTaskResponseDTO = new PluginCreateTaskResponseDTO()
            pluginTaskResponseDTO.url = resUrlList[0]
            const worksName = unifiedWorksInfo.title
            taskPluginData.worksName = worksName
            pluginTaskResponseDTO.taskName = worksName
            pluginTaskResponseDTO.pluginData = { ...taskPluginData }
            pluginTaskResponseDTO.siteWorksId = idMatch[1]
            pluginTaskResponseDTO.siteDomain = PixivTaskHandler.PIXIV_DOMAIN
            pluginTaskResponseDTO.continuable = false
            childTasks.push(pluginTaskResponseDTO)
        }
    } else {
        let no = 1
        for (const resUrl of resUrlList) {
            const idMatch = resUrl.match(/https:\/\/i\.pximg\.net\/img-original\/img\/\d{4}\/\d{2}\/\d{2}\/\d{2}\/\d{2}\/\d{2}\/(\d+_p\d+)\.[a-zA-Z]+/)
            if (!idMatch) {
                this.pluginTool.pluginLogUtil.warn('找不到资源url')
                continue
            }
            if (!idMatch[1]) {
                this.pluginTool.pluginLogUtil.warn('资源url中未匹配到id')
                continue
            }
            const pluginTaskResponseDTO = new PluginCreateTaskResponseDTO()
            pluginTaskResponseDTO.url = resUrl
            const worksName = unifiedWorksInfo.title + '[' + no + ']'
            taskPluginData.worksName = worksName
            pluginTaskResponseDTO.taskName = worksName
            pluginTaskResponseDTO.pluginData = { ...taskPluginData }
            pluginTaskResponseDTO.siteWorksId = idMatch[1]
            pluginTaskResponseDTO.siteDomain = PixivTaskHandler.PIXIV_DOMAIN
            pluginTaskResponseDTO.continuable = false
            childTasks.push(pluginTaskResponseDTO)
            no++
        }
    }
    return [parentTask]
  }

  /**
   * 生成作品信息
   * @param task : Task 任务
   * @return Promise<PluginWorksResponseDTO>
   */
  async createWorksInfo(task) {
    const unifiedWorksInfo = task.pluginData.unifiedWorksInfo
    const pluginWorksResponseDTO = new PluginWorksResponseDTO()
    // 作品信息
    pluginWorksResponseDTO.works.siteWorksName = task.pluginData.worksName
    pluginWorksResponseDTO.works.siteWorkDescription = unifiedWorksInfo.description
    pluginWorksResponseDTO.works.siteWorksId = task.siteWorksId
    // 作者信息
    pluginWorksResponseDTO.siteAuthors = task.pluginData.siteAuthor
    // 标签信息
    pluginWorksResponseDTO.siteTags = task.pluginData.siteTags
    return pluginWorksResponseDTO
  }

  /**
   * 开始任务
   * @param task : Task 需开始的任务
   * @return Promise<PluginWorksResponseDTO>
   */
  async start(task) {
    const responseDTO = new PluginWorksResponseDTO()
    // 这些属性设为undefined，否则会导致createWorksInfo创建的作者和标签被取消关联
    responseDTO.localAuthors = undefined
    responseDTO.localTags = undefined
    responseDTO.siteAuthors = undefined
    responseDTO.siteTags = undefined

    const requestPixivResource = async () => {
      const response = await fetch(task.url, { readAsStream: true, headers: {'Referer': 'https://www.pixiv.net/'} })
      if (!response.ok) {
        throw new Error(`Failed to fetch data: ${response.statusText}`)
      }
      return response
    }

    responseDTO.resource.resourceStream = new PixivRetryReadable(requestPixivResource, 3, this.pluginTool.pluginLogUtil)

    responseDTO.doUpdate = true

    // 获取响应头的一些信息
    const headers = await responseDTO.resource.resourceStream.getHeaders()
    // 文件大小
    responseDTO.resource.resourceSize = headers.get('content-length')
    // 扩展名
    const fileType = headers.get('content-type')
    switch (fileType) {
      case 'image/jpg':
        responseDTO.resource.filenameExtension = '.jpg'
        break
      case 'image/jpeg':
        responseDTO.resource.filenameExtension = '.jpeg'
        break
      case 'image/png':
        responseDTO.resource.filenameExtension = '.png'
        break
      case 'video/mp4':
        responseDTO.resource.filenameExtension = '.mp4'
        break
    }
    // 建议名称
    const contentDisposition = headers.get('content-disposition')
    if (contentDisposition) {
      // 使用正则表达式匹配 'filename' 或 'filename*' 参数
      const regex = /filename=["']?([^"'\s]+)["']?|filename\*=UTF-8''([^\s]+)/
      const match = contentDisposition.match(regex)

      if ((match && match[1]) || match[2]) {
        // 提取文件名
        responseDTO.resource.suggestedName = match[1] || decodeURIComponent(match[2])
      }
    }

    return responseDTO
  }

  /**
   * 重试下载任务
   * @param task 需要重试的任务
   * @return 作品信息
   */
  retry(task) {}

  /**
   * 暂停下载任务
   * @param taskResParam { PluginTaskResParam } 需要暂停的任务和资源数据
   * @return 作品信息
   */
  pause(taskResParam) {
    if (taskResParam.resourcePluginDTO.resourceStream !== undefined && taskResParam.resourcePluginDTO.resourceStream !== null) {
      taskResParam.resourcePluginDTO.resourceStream.pause()
    }
  }

  /**
   * 停止下载任务
   * @param taskResParam { PluginTaskResParam } 需要停止的任务和资源数据
   * @return 作品信息
   */
  stop(taskResParam) {
    if (taskResParam.resourcePluginDTO.resourceStream !== undefined && taskResParam.resourcePluginDTO.resourceStream !== null) {
      taskResParam.resourcePluginDTO.resourceStream.destroy()
    }
  }

  /**
   * 恢复下载任务
   * @param taskResParam { PluginTaskResParam } 需要恢复的任务
   * @return 作品信息
   */
  async resume(taskResParam) {
    // 如果资源流还存在，直接使用这个流
    if (taskResParam.resourcePluginDTO.resourceStream !== undefined && taskResParam.resourcePluginDTO.resourceStream !== null) {
      const newResourcePluginDTO = new ResourcePluginDTO()
      newResourcePluginDTO.resourceStream = taskResParam.resourcePluginDTO.resourceStream
      newResourcePluginDTO.resourceSize = taskResParam.resourcePluginDTO.resourceSize
      newResourcePluginDTO.filenameExtension = taskResParam.resourcePluginDTO.filenameExtension
      newResourcePluginDTO.continuable = true
      const response = new PluginWorksResponseDTO()
      response.resource = newResourcePluginDTO
      response.doUpdate = false
      return response
    }
    let downloadedBytes = 0
    try {
      downloadedBytes = fs.statSync(taskResParam.task.pendingSavePath).size
    } catch (ignoredError) {}

    const requestPixivResource = async () => {
      const response = await fetch(taskResParam.task.url, { readAsStream: true, headers: {'Referer': 'https://www.pixiv.net/'} })
      if (!response.ok) {
        throw new Error(`Failed to fetch data: ${response.statusText}`)
      }
      return response
    }
    const stream = new PixivRetryReadable(requestPixivResource, 3, this.pluginTool.pluginLogUtil)

    return stream.data
  }

  /**
   * 登录pixiv
   * @return {Promise<void>}
   */
  async loginPixiv() {
    return this.cachePixivToken(await new PixivLogin(this.pluginTool).loginPixiv())
  }

  /**
   * 刷新token
   * @return {Promise<PixivTokens>}
   */
  async refreshToken() {
    const refreshToken = this.pixivTokens.refreshToken
    if (refreshToken === undefined) {
      await this.loginPixiv()
      return this.pixivTokens
    } else {
      try {
        this.cachePixivToken(await PixivLogin.refreshToken(refreshToken))
      } catch (error) {
        if (error instanceof AccessTokenRefreshFailed) {
          this.pluginTool.pluginLogUtil.info(`refreshToken过期或错误，清除缓存的refreshToken，请求用户登录`)
          this.pixivTokens.refreshToken = undefined
          await this.loginPixiv()
          return this.pixivTokens
        } else {
          throw error
        }
      }
    }
  }

  /**
   * 判断token缓存情况，解决认证问题
   * @return {Promise<void>}
   */
  async resolveOAuth() {
    const pixivTokens = this.pixivTokens
    const accessToken = pixivTokens.accessToken
    const refreshToken = pixivTokens.refreshToken
    const accessTokenExpired = pixivTokens.lastRefresh + pixivTokens.expiresIn * 1000 <= Date.now()
    const accessTokenCached = accessToken !== undefined && accessToken !== null && accessToken !== ''
    const refreshTokenCached = refreshToken !== undefined && refreshToken !== null && refreshToken !== ''
    if (!accessTokenCached || accessTokenExpired) {
      let msg
      if (accessTokenExpired) {
        msg = '缓存的accessToken已过期'
      } else {
        msg = '没有缓存的accessToken'
      }
      if (refreshTokenCached) {
        this.pluginTool.pluginLogUtil.info(`${msg}，使用refreshToken刷新`)
        await this.refreshToken()
      } else {
        this.pluginTool.pluginLogUtil.info(`${msg}，没有缓存的refreshToken，请求用户进行登录`)
        await this.loginPixiv()
      }
    }
  }

  /**
   * 缓存令牌
   * @param pixivToken
   * @return {Promise<number>}
   */
  async cachePixivToken(pixivToken) {
    this.pixivTokens = pixivToken
    return this.pluginTool.setPluginData(JSON.stringify(this.pixivTokens))
  }
}
